/*
 * Copyright (c) 2024 iSOFT INFRASTRUCTURE SOFTWARE CO., LTD.
 * easyAda is licensed under Mulan PubL v2.
 * You can use this software according to the terms and conditions of the Mulan PubL v2.
 * You may obtain a copy of Mulan PubL v2 at:
 *          http://license.coscl.org.cn/MulanPubL-2.0
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PubL v2 for more details.
 */

#include <plat/config.h>
#include <arch/exception.h>
#include <stdlib/assert.h>
#include <tools/macros.h>
#include <core/interrupt.h>
#include <core/notification.h>
#include <core/schedule.h>
#include <core/dprintf.h>
#include <core/task.h>

static notification_t irqtable[CONFIG_IRQ_COUNT];

static inline void mark_unlocked(unsigned int irq)
{
    notification_set_private_data(&(irqtable[irq]), 0);
}

static inline int is_locked(unsigned int irq)
{
    return (notification_get_private_data(&(irqtable[irq])) != 0UL) ? 1 : 0;
}

notification_t *irq_register(unsigned int irq)
{
    notification_t *ntfn = NULL;

    if ((irq < (unsigned int)CONFIG_IRQ_COUNT) && ((~is_locked(irq)) != 0)) {
        ntfn = &(irqtable[irq]);
        (void)notification_get(ntfn);
    }

    return ntfn;
}

void irq_unregister(notification_t *ntfn)
{
    /* PRQA S 2668 1 */
    unsigned int irq = (unsigned int)(ntfn - irqtable);

    if (notification_put(ntfn) == 1) {
        exception_disable_irq(irq);
        exception_finish(irq);

        if (notification_has_waiters(ntfn) != 0) {
            notification_cancel(ntfn);
            notification_init(ntfn);
        }
    }
}

void irq_prepare_wait(notification_t *ntfn)
{
    /* PRQA S 2668 1 */
    unsigned int irq = (unsigned int)(ntfn - irqtable);

    exception_enable_irq(irq);
}

void irq_ack_enable(notification_t *ntfn)
{
    /* PRQA S 2668 1 */
    unsigned int irq = (unsigned int)(ntfn - irqtable);

    exception_finish(irq);

    if (object_refcount(&ntfn->obj) > 1) {
        exception_enable_irq(irq);
    }
}

void irq_handler_entry(unsigned int irq)
{
    notification_t *ntfn = irqtable + irq;

    if (irq >= CONFIG_IRQ_COUNT) {
        return;
    }

    if (is_locked(irq) != 0) {
        handler_t handler = (handler_t)notification_get_private_data(ntfn);
        handler();
        exception_finish(irq);
        return;
    }

    exception_disable_irq(irq);

    if (object_refcount(&ntfn->obj) > 1) {
        exception_finish(irq);

        notification_signal(ntfn, NULL);
    } else {
        dprintf("Unexpected irq %d received! Disable it and ignore.\n", irq);
        exception_finish(irq);
    }

    schedule();
}

void irq_handler_init(void)
{
    unsigned int i;
    for (i = 0; i < CONFIG_IRQ_COUNT; i++) {
        notification_init(&(irqtable[i]));
        (void)notification_get(&(irqtable[i]));
        mark_unlocked(i);
    }
}

void irq_setup_handler(unsigned int irq, handler_t handler)
{
    assert(irq < CONFIG_IRQ_COUNT);
    notification_set_private_data(&(irqtable[irq]), (unsigned long)handler);
    exception_enable_irq(irq);
}

void irq_clean_handler(unsigned int irq)
{
    assert(irq < CONFIG_IRQ_COUNT);
    mark_unlocked(irq);
    exception_disable_irq(irq);
}
